﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace Model
{
    /// <summary>
    /// 客户端连接通道
    /// </summary>
    public class TChannel:IDisposable
    {
        public long Id { get; set; }
        protected bool isConnected;
        private bool isSending;
        private TcpClient tcpClient;
        private TService service;
        public IPEndPoint RemoteAddress { get; protected set; }
        private event Action<TChannel, SocketError> errorCallback;
        public event Action<TChannel,SocketError> ErrorCallback
        {
            add
            {
                this.errorCallback += value;
            }
            remove
            {
                this.errorCallback -= value;
            }
        }

        // 环形缓冲区
        private readonly Circularbuffer receRingBuffer = new Circularbuffer();
        private readonly Circularbuffer sendRingBuffer = new Circularbuffer();

        private readonly byte[] recvTempBuffer = new byte[8190];
        private readonly PacketParser parser;
        private TaskCompletionSource<Packet> recvTcs;

        /// <summary>
        /// 服务端监听
        /// </summary>
        /// <param name="service"></param>
        /// <param name="client"></param>
        public TChannel(TService service,TcpClient client)
        {
            this.Id = IdGenerater.GeneraterId();
            this.tcpClient = client;
            this.parser = new PacketParser(this.receRingBuffer);

            this.service = service;
            this.RemoteAddress = (IPEndPoint)this.tcpClient.Client.RemoteEndPoint;

            // 开始
            this.OnAccepted();
        }

        /// <summary>
        /// 客户端连接
        /// </summary>
        /// <param name="service"></param>
        /// <param name="tcpClient"></param>
        /// <param name="iPEndPoint"></param>
        public TChannel(TService service,TcpClient tcpClient,IPEndPoint iPEndPoint)
        {
            this.Id = IdGenerater.GeneraterId();
            this.tcpClient = tcpClient;
            this.parser = new PacketParser(this.receRingBuffer);
            this.RemoteAddress = iPEndPoint;
            this.ConnectAsync(iPEndPoint);
        }

        /// <summary>
        /// 异步连接
        /// </summary>
        /// <param name="iPEndPoint"></param>
        public async void ConnectAsync(IPEndPoint iPEndPoint)
        {
            try
            {
                await this.tcpClient.ConnectAsync(iPEndPoint.Address, iPEndPoint.Port);
                this.isConnected = true;
                this.StartSend();
                this.StartRecv();
            }
            catch(SocketException se)
            {
                Log.Error($"connect error: {se.Message}");
            }
            catch (Exception e)
            {
                Log.Error($"connect error: {e.Message}");
            }
        }

        public void OnAccepted()
        {
            this.isConnected = true;
            this.StartRecv();
        }

        /// <summary>
        /// 开始接受数据
        /// </summary>
        private async void StartRecv()
        {
            try
            {
                while (true)
                {
                    if(this.Id == 0)
                    {
                        return;
                    }

                    // 写入块中，可以写入的大小。
                    int size = this.receRingBuffer.ChunkSize - this.receRingBuffer.LastIndex;

                    // 读取长度
                    int len = await this.tcpClient.GetStream().ReadAsync(this.receRingBuffer.Last, this.receRingBuffer.LastIndex, size);

                    if(len == 0)
                    {
                        this.OnError(this, SocketError.NetworkReset);
                        return;
                    }

                    this.receRingBuffer.LastIndex += len;

                    if(this.receRingBuffer.LastIndex == this.receRingBuffer.ChunkSize)
                    {
                        this.receRingBuffer.AddLast();
                        this.receRingBuffer.LastIndex = 0;
                    }

                    if(this.recvTcs != null) // 表示有方法在调用Recv异步方法取数据
                    {
                        bool isOK = this.parser.Parser();
                        if (isOK)
                        {
                            Packet packet = this.parser.GetPacket();
                            var tcs = this.recvTcs;
                            this.recvTcs = null;
                            tcs.SetResult(packet);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Log.Error(e.Message);
                OnError(this, SocketError.SocketError);
            }
        }

        /// <summary>
        /// 取一个数据
        /// </summary>
        public Task<Packet> Recv()
        {
            if(this.Id == 0)
            {
                throw new Exception("TChannel已经被Dispose");
            }

            bool isOK = parser.Parser();

            if (isOK)
            {
                Packet packet = this.parser.GetPacket();
                return Task.FromResult(packet);
            }
            
            recvTcs = new TaskCompletionSource<Packet>();
            return recvTcs.Task;
        }

        public void Send(byte[] buffer)
        {
            if (this.Id == 0)
            {
                throw new Exception("TChannel已经被Dispose, 不能发送消息");
            }

            byte[] size = BitConverter.GetBytes((ushort)buffer.Length);
            this.sendRingBuffer.SendTo(size);
            this.sendRingBuffer.SendTo(buffer);
            if (this.isConnected)
            {
                this.StartSend();
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffers"></param>
        public void Send(List<byte[]> buffers)
        {
            if(this.Id == 0)
            {
                throw new Exception("TChannel已经被Dispose, 不能发送消息");
            }

            ushort size = (ushort)buffers.Select(b => b.Length).Sum();
            byte[] sizeBuffer = BitConverter.GetBytes(size);
            this.sendRingBuffer.SendTo(sizeBuffer);
            foreach (var item in buffers)
            {
                this.sendRingBuffer.SendTo(item);
            }

            if (this.isConnected)
            {
                this.StartSend();
            }
        }



        /// <summary>
        /// 开始发送数据
        /// </summary>
        private async void StartSend()
        {
            try
            {
                if (this.Id == 0)
                    return;

                if (this.isSending)
                    return;

                while (true)
                {
                    if (this.Id == 0)
                        return;

                    if(this.sendRingBuffer.Count == 0)
                    {
                        this.isSending = false;
                        return;
                    }

                    this.isSending = true;

                    int sendSize = sendRingBuffer.ChunkSize - sendRingBuffer.FirstIndex;

                    if (sendSize > sendRingBuffer.Count)
                        sendSize = sendRingBuffer.Count;

                    await this.tcpClient.GetStream().WriteAsync(sendRingBuffer.First, sendRingBuffer.FirstIndex, sendSize);
                    this.sendRingBuffer.FirstIndex += sendSize;

                    if(this.sendRingBuffer.FirstIndex == this.sendRingBuffer.ChunkSize)
                    {
                        this.sendRingBuffer.RemoveFirst();
                        this.sendRingBuffer.FirstIndex = 0;
                    }
                }

            }
            catch (Exception e)
            {
                Log.Error(e.ToString());
                this.OnError(this, SocketError.SocketError);
            }
        }

        /// <summary>
        /// Socket错误调用
        /// </summary>
        /// <param name="channel"></param>
        /// <param name="e"></param>
        protected void OnError(TChannel channel,SocketError e)
        {
            this.errorCallback?.Invoke(channel, e);
        }

        public void Dispose()
        {
            if(this.Id == 0)
            {
                return;
            }

            long id = this.Id;
            this.Id = 0;
            this.service.Remove(id);

            this.tcpClient.Close();
        }
    }
}
